package com.moonshineproject.text
{
	import com.moonshineproject.text.change.*;
	import com.moonshineproject.text.events.ChangeEvent;
	import com.moonshineproject.utils.TextUtil;
	import com.moonshineproject.vo.Settings;
	
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.ui.Keyboard;
	import flash.ui.Mouse;
	import flash.ui.MouseCursor;
	
	public class SelectionManager
	{
		private var editor:TextEditor;
		private var model:TextEditorModel;
		
		private var dragStartChar:int = -1;
		private var dragStartLine:int = -1;
		
		public function SelectionManager(editor:TextEditor, model:TextEditorModel)
		{
			this.editor = editor;
			this.model = model;
			
			editor.addEventListener(ChangeEvent.TEXT_CHANGE, handleChange);
			editor.addEventListener(KeyboardEvent.KEY_DOWN, handleKeyDown);
			editor.addEventListener(Event.SELECT_ALL, handleSelectAll);
			
			editor.itemContainer.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
			editor.itemContainer.addEventListener(MouseEvent.MOUSE_OVER, updateCursor);
			editor.itemContainer.addEventListener(MouseEvent.MOUSE_MOVE, updateCursor);
			editor.itemContainer.addEventListener(MouseEvent.MOUSE_OUT, resetCursor);
		}

		private function handleMouseDown(event:MouseEvent):void
		{
			var stagePoint:Point = new Point(event.stageX, event.stageY);
			var localPoint:Point = editor.globalToLocal(stagePoint);
			
			var startLine:int;
			var startChar:int;
			var endLine:int;
			var endChar:int;
			
			var rdr:TextLineRenderer = getRendererAtPoint(stagePoint);
			if (!rdr) return;
			
			var newCaretPosition:int = rdr.getCharIndexFromPoint(stagePoint.x);
			if (newCaretPosition > -1)
			{
				startLine = event.shiftKey ? model.hasSelection ? model.selectionStartLineIndex : model.selectedLineIndex : rdr.dataIndex;
				startChar = event.shiftKey ? model.hasSelection ? model.selectionStartCharIndex : model.caretIndex : newCaretPosition;
				
				endLine = rdr.dataIndex;
				endChar = newCaretPosition;
			}
			else if (localPoint.x < editor.lineNumberWidth && localPoint.x > 16)
			{
				startLine = event.shiftKey ? model.hasSelection ? model.selectionStartLineIndex : model.selectedLineIndex : rdr.dataIndex;
				startChar = event.shiftKey ? model.hasSelection ? model.selectionStartCharIndex : model.caretIndex : 0;
				
				endLine = rdr.dataIndex + (event.shiftKey && (startLine > rdr.dataIndex || startLine == rdr.dataIndex && startChar > 0) ? 0 : 1);
				endChar = 0;
				
				if (endLine >= model.lines.length)
				{
					endLine = model.lines.length - 1;
					endChar = model.lines[endLine].text.length;
				}
			}
			else if (localPoint.x >= 0 && localPoint.x <= 16)
			{
				toggleBreakpoint(rdr.dataIndex);
				return;
			}
			else return;
			
			model.setSelection(startLine, startChar, endLine, endChar);
			editor.invalidateSelection();
			
			dragStartLine = startLine;
			dragStartChar = startChar;
			editor.stage.addEventListener(MouseEvent.MOUSE_MOVE, handleMouseMove);
			editor.stage.addEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
		}
		
		private function handleMouseUp(event:MouseEvent):void
		{
			editor.stage.removeEventListener(MouseEvent.MOUSE_MOVE, handleMouseMove);
			editor.stage.removeEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
			
			dragStartLine = -1;
			dragStartChar = -1;
		}
		
		private function handleMouseMove(event:MouseEvent):void
		{
			var stagePoint:Point = new Point(event.stageX, event.stageY);
			var localPoint:Point = editor.globalToLocal(stagePoint);
			
			var startLine:int = dragStartLine;
			var startChar:int = dragStartChar;
			var endLine:int;
			var endChar:int;
			
			var rdr:TextLineRenderer = getRendererAtPoint(stagePoint);
			if (!rdr) return;
			
			var newCaretPosition:int = rdr.getCharIndexFromPoint(stagePoint.x);
			if (newCaretPosition > -1)
			{
				endLine = rdr.dataIndex;
				endChar = newCaretPosition;
			}
			else if (localPoint.x < editor.lineNumberWidth)
			{
				endLine = rdr.dataIndex + (startLine > rdr.dataIndex || startLine == rdr.dataIndex && startChar > 0 ? 0 : 1);
				endChar = 0;
				
				if (endLine >= model.lines.length)
				{
					endLine = model.lines.length - 1;
					endChar = model.lines[endLine].text.length;
				}
			}
			else return;
			
			model.setSelection(startLine, startChar, endLine, endChar);
			editor.invalidateSelection();
		}
		
		private function updateCursor(event:MouseEvent):void
		{
			var stagePoint:Point = new Point(event.stageX, event.stageY);
			var localPoint:Point = editor.globalToLocal(stagePoint);
			
			// This should actually be a mirrored arrow over the line-numbers
			Mouse.cursor = localPoint.x < editor.lineNumberWidth ? MouseCursor.ARROW : MouseCursor.IBEAM;
		}
		
		private function resetCursor(event:MouseEvent):void
		{
			Mouse.cursor = MouseCursor.AUTO;
		}
		
		private function handleKeyDown(event:KeyboardEvent):void
		{
			var chars:int = 1;
			var word:Boolean = event[Settings.keyboard.wordModifierKey];
			
			switch (event.keyCode)
			{
				case Keyboard.LEFT:
				{
					if (model.hasSelection && !event.shiftKey)
					{
						if (model.selectedLineIndex > model.selectionStartLineIndex
							|| model.caretIndex > model.selectionStartCharIndex)
						{
							focusSelectionStart();
						}
						
						removeSelection();					
					}
					else if (model.caretIndex > 0)
					{
						if (word)
						{
							chars = TextUtil.wordBoundaryBackward(model.selectedLine.text.substring(0, model.caretIndex));
						}
						startSelectionIfNeeded(event.shiftKey);
						model.caretIndex -= chars;
					}
					else
					{
						if (model.selectedLineIndex == 0) return;
						startSelectionIfNeeded(event.shiftKey);
						
						model.selectedLineIndex--;
						model.caretIndex = model.selectedLine.text.length;
						
						scrollViewIfNeeded();
					}
					break;
				}
				case Keyboard.RIGHT:
				{
					if (model.hasSelection && !event.shiftKey)
					{
						if (model.selectedLineIndex < model.selectionStartLineIndex
							|| model.caretIndex < model.selectionStartCharIndex)
						{
							focusSelectionStart();
						}
						removeSelection(); 
					}
					else if (model.caretIndex < model.selectedLine.text.length)
					{
						if (word)
						{
							chars = TextUtil.wordBoundaryForward(model.selectedLine.text.substring(model.caretIndex));
						}
						
						startSelectionIfNeeded(event.shiftKey);
						model.caretIndex += chars;
					}
					else if (model.selectedLineIndex < model.lines.length-1)
					{
						startSelectionIfNeeded(event.shiftKey);
						
						model.selectedLineIndex++;
						model.caretIndex = 0;
						
						scrollViewIfNeeded();
					}
					break;
				}
				case Keyboard.UP:
				{
					if (event.controlKey)
					{
						if (model.scrollPosition > 0)
						{
							// Ensure the caret stays in view (unless there's active selection)
							if (!model.hasSelection && model.selectedLineIndex > model.scrollPosition + model.renderersNeeded - 3)
							{
								model.selectedLineIndex = model.scrollPosition + model.renderersNeeded - 3;
							}
							editor.scrollTo(model.scrollPosition - 1);
						}
					}
					else
					{
						if (model.hasSelection && !event.shiftKey) removeSelection();					
						if (model.selectedLineIndex > 0)
						{
							startSelectionIfNeeded(event.shiftKey);
							
							model.selectedLineIndex--;
							
							scrollViewIfNeeded();
						}
					}
					
					break;
				}
				case Keyboard.DOWN:
				{
					if (event.controlKey)
					{
						if (model.scrollPosition < model.lines.length - model.renderersNeeded + 1)
						{
							// Ensure the caret stays in view (unless there's active selection)
							if (!model.hasSelection && model.selectedLineIndex < model.scrollPosition + 1)
							{
								model.selectedLineIndex = model.scrollPosition + 1;
							}
							editor.scrollTo(model.scrollPosition + 1);
						}
					}
					else
					{
						if (model.hasSelection && !event.shiftKey) removeSelection();
						if (model.selectedLineIndex < model.lines.length-1)
						{
							startSelectionIfNeeded(event.shiftKey);
							
							model.selectedLineIndex++;
							
							scrollViewIfNeeded();
						}
					}
					
					break;
				}
				case Keyboard.PAGE_DOWN:
				{
					if (model.hasSelection && !event.shiftKey) removeSelection();
					startSelectionIfNeeded(event.shiftKey);
					
					if (event.controlKey)
					{
						model.selectedLineIndex = model.scrollPosition + model.renderersNeeded - 2;
					}
					else
					{
						model.selectedLineIndex = Math.min(model.selectedLineIndex + model.renderersNeeded, model.lines.length - 1);
						
						editor.scrollTo(model.scrollPosition + model.renderersNeeded);
					}
					
					break;
				}
				case Keyboard.PAGE_UP:
				{
					if (model.hasSelection && !event.shiftKey) removeSelection();
					startSelectionIfNeeded(event.shiftKey);
					
					if (event.controlKey)
					{
						model.selectedLineIndex = model.scrollPosition;
					}
					else
					{
						model.selectedLineIndex = Math.max(model.selectedLineIndex - model.renderersNeeded, 0);
					
						editor.scrollTo(model.scrollPosition - model.renderersNeeded);
					}
					
					break;
				}
				case Keyboard.HOME:
				{
					if (model.hasSelection && !event.shiftKey) removeSelection();
					startSelectionIfNeeded(event.shiftKey);
					
					if (event.controlKey)
					{
						model.selectedLineIndex = 0;
						model.caretIndex = 0;
						scrollViewIfNeeded();
					}
					else
					{
						var tabIndex:int = TextUtil.indentAmount(model.selectedLine.text);
						
						if (model.caretIndex == tabIndex) model.caretIndex = 0;
						else model.caretIndex = tabIndex;
					}
					break;
				}
				case Keyboard.END:
				{
					if (model.hasSelection && !event.shiftKey) removeSelection();
					startSelectionIfNeeded(event.shiftKey);
					
					if (event.controlKey)
					{
						model.selectedLineIndex = model.lines.length - 1;
						model.caretIndex = model.selectedLine.text.length;
						scrollViewIfNeeded();
					}
					else
					{
						if (model.caretIndex < model.selectedLine.text.length) model.caretIndex = model.selectedLine.text.length;
					}
					break;
				}
			}
			editor.invalidateSelection();
		}
		
		private function handleSelectAll(event:Event):void
		{
			model.setSelection(0, 0, model.lines.length-1, model.lines[model.lines.length-1].text.length);
			scrollViewIfNeeded();
		}
		
		private function handleChange(event:ChangeEvent):void
		{
			if (event.origin != ChangeEvent.ORIGIN_REMOTE)
			{
				model.removeSelection();
				
				applyChange(event.change);
				
				scrollViewIfNeeded();
			}
		}
		
		private function applyChange(change:TextChangeBase):void
		{
			if (change is TextChangeInsert) applyChangeInsert(TextChangeInsert(change));
			if (change is TextChangeRemove) applyChangeRemove(TextChangeRemove(change));
			if (change is TextChangeMulti) applyChangeMulti(TextChangeMulti(change));
		}
		
		private function applyChangeInsert(change:TextChangeInsert):void
		{
			var textLines:Vector.<String> = change.textLines;

			if (textLines && textLines.length)
			{
				// Set caret to the end of the text change
				model.selectedLineIndex = change.startLine + textLines.length - 1;
				model.caretIndex = (textLines.length == 1 ? change.startChar : 0) + textLines[textLines.length-1].length;
			}
		}
		
		private function applyChangeRemove(change:TextChangeRemove):void
		{
			model.selectedLineIndex = change.startLine;
			model.caretIndex = change.startChar;
		}
		
		private function applyChangeMulti(change:TextChangeMulti):void
		{
			for each (var subchange:TextChangeBase in change.changes)
			{
				applyChange(subchange);
			}
		}
		
		private function removeSelection():void
		{
			model.removeSelection();
		}
		
		private function focusSelectionStart():void
		{
			model.selectedLineIndex = model.selectionStartLineIndex;
			model.caretIndex = model.selectionStartCharIndex;
		}
		
		private function startSelectionIfNeeded(shiftKey:Boolean):void
		{
			if (!model.hasSelection && shiftKey)
			{
				model.selectionStartLineIndex = model.selectedLineIndex;
				model.selectionStartCharIndex = model.caretIndex;
			}
		}
		
		private function toggleBreakpoint(lineIndex:int):void
		{
			var model:TextLineModel = model.lines[lineIndex]; 
			model.breakPoint = !model.breakPoint;
			editor.invalidateLines();
		}
		
		private function scrollViewIfNeeded():void
		{
			if (model.selectedLineIndex < model.scrollPosition)
			{
				editor.scrollTo(model.selectedLineIndex);
			}
			else if (model.selectedLineIndex + 2 > model.scrollPosition + model.renderersNeeded)
			{
				editor.scrollTo(model.selectedLineIndex-model.renderersNeeded+2);
			}
		}
		
		private function getRendererAtPoint(stagePoint:Point):TextLineRenderer
		{
			var count:int = model.itemRenderersInUse.length;
			
			for (var i:int = 0; i < count; i++)
			{
				var rdr:TextLineRenderer = model.itemRenderersInUse[i];
				var rect:Rectangle = rdr.getRect(editor.stage);
				
				if ((stagePoint.y >= rect.top || i == 0) && (stagePoint.y <= rect.bottom || i == count-1))
				{
					return rdr;
				}
			}
			
			return null;
		}
	}
}